`include "defines.v"

module CSR (
    input  wire              clock,
    input  wire              reset,

    input  wire              inst_fetched,
    input  wire              flush,
    input  wire [3:0]        stall,

    //exception
    input  wire              inst_ecall,
    input  wire              inst_mret,
    input  wire              intr,
    input  wire              pc_valid,
    input  wire              inst_jump_or_branch_taken_flag,
    input  wire [63: 0]      epc_ex,
    input  wire [63: 0]      epc_mem,
    input  wire              mem_r_req,
    input  wire              mem_w_req,
    output wire              exception_jump_flag,
    output wire [63: 0]      exception_jump_addr,

    //read port
    input  wire              csr_r_en,
    input  wire [11: 0]      csr_r_addr,
    output wire [`MXLEN-1:0] csr_r_data,

    //write port
    input  wire              csr_w_en,
    input  wire [11: 0]      csr_w_addr,
    input  wire [`MXLEN-1:0] csr_w_data,

    output reg  [ 5: 0]      mtimefreq,

    //for sim
    output wire              trap,
    //Machine Information Registers
    output reg [`MXLEN-1:0]  mhartid,

    //Machine Trap Setup
    output reg [`MXLEN-1:0]  mstatus,
    output reg [`MXLEN-1:0]  mie,
    output reg [`MXLEN-1:0]  mtvec,

    //Machine Trap Handling
    output reg [`MXLEN-1:0]  mscratch,
    output reg [`MXLEN-1:0]  mepc,
    output reg [`MXLEN-1:0]  mcause,
    output reg [`MXLEN-1:0]  mip,
 
    //Machine Counter/Timers
    output reg [`MXLEN-1:0]  mcycle,
    output reg [`MXLEN-1:0]  minstret  //TODO:delete before soc
);
    assign trap = csr_state == timer_intr_operate;//TODO:add before soc
    ////Machine Information Registers //TODO:add before soc
    //reg [`MXLEN-1:0] mhartid;
    //
    ////Machine Trap Setup
    //reg [`MXLEN-1:0] mstatus;
    //reg [`MXLEN-1:0] mie;
    //reg [`MXLEN-1:0] mtvec;
    //
    ////Machine Trap Handling
    //reg [`MXLEN-1:0] mscratch;
    //reg [`MXLEN-1:0] mepc;
    //reg [`MXLEN-1:0] mcause;
    //reg [`MXLEN-1:0] mip;
    //
    ////Machine Counter/Timers
    //reg [`MXLEN-1:0] mcycle;
    //reg [`MXLEN-1:0] minstret; //TODO:realize minstret before soc

    //----------State Machine----------//
    localparam idle               = 3'b000;
    localparam env_call_operate   = 3'b011;
    localparam timer_intr_pre     = 3'b100;
    localparam timer_intr_operate = 3'b101;
    localparam mret_operate       = 3'b001;

    reg [2:0] csr_state;
    reg [2:0] next_state;
    reg [`BUSLEN-1:0] mepc_pre;
    reg [3:0] inst_valid;

    always @(*) begin
        case(csr_state)
            idle               : next_state = inst_ecall                          ?  env_call_operate  :
                                              (t_intr & ~mem_w_req & ~mem_r_req)  ?  timer_intr_pre    : 
                                              inst_mret                           ?  mret_operate      :  idle  ;
            env_call_operate   : next_state = idle;
            timer_intr_pre     : next_state = timer_intr_operate;
            timer_intr_operate : next_state = idle;
            mret_operate       : next_state = idle;
            default            : next_state = idle;
        endcase
    end

    always @(posedge clock) begin
        if(reset) csr_state <= idle;
        else      csr_state <= next_state;
    end

    always @(posedge clock) begin
        if(reset)      inst_valid      <= 0;
        else if(flush) begin
            inst_valid[3]   <= inst_valid[2];
            inst_valid[2:0] <= 0;
        end
        else begin
            if(~stall[2]) inst_valid[3] <= inst_valid[2];
            if(~stall[1]) inst_valid[2] <= inst_valid[1];
            if(~stall[0]) inst_valid[1] <= inst_valid[0];
            inst_valid[0] <= inst_fetched;
        end
    end

    always @(posedge clock) begin
        if(reset) begin
            mhartid   <= 0;
            mstatus   <= 0;
            mie       <= 0;
            mtvec     <= 0;
            mscratch  <= 0;
            mepc      <= 0;
            mcause    <= 0;
            mip       <= 0;
            mcycle    <= 0;
            minstret  <= 0;
            mtimefreq <= 6'd9;
        end
        else begin
            if(csr_state == idle) mepc_pre <= (inst_ecall | inst_jump_or_branch_taken_flag) ? epc_mem : epc_ex;
            if(csr_w_en & (csr_state == idle | csr_state == timer_intr_pre)) begin
                if(csr_w_addr == `mstatus_addr)   mstatus   <= {csr_w_data[14] & csr_w_data[13],csr_w_data[`MXLEN-2:17],2'b0,csr_w_data[14:0]};
                if(csr_w_addr == `mepc_addr)      mepc      <= csr_w_data;
                if(csr_w_addr == `mcause_addr)    mcause    <= csr_w_data;
                if(csr_w_addr == `mie_addr)       mie       <= csr_w_data;
                if(csr_w_addr == `mtvec_addr)     mtvec     <= csr_w_data;
                if(csr_w_addr == `mscratch_addr)  mscratch  <= csr_w_data;
                if(csr_w_addr == `mip_addr)       mip       <= { csr_w_data[`MXLEN-1:`MIP_MTIP+1] , 1'b0 , csr_w_data[`MIP_MTIP-1:0] };
                if(csr_w_addr == `mcycle_addr)    mcycle    <= csr_w_data;
                else                              mcycle <= mcycle + 64'b1;
                if(csr_w_addr == `minstret_addr)  minstret  <= csr_w_data;
                if(csr_w_addr == `mtimefreq_addr) mtimefreq <= csr_w_data[5:0]; 
            end
            else if(csr_state == env_call_operate | csr_state == timer_intr_operate | csr_state == mret_operate) begin
                mstatus[`MSTATUS_MPP]  <= (csr_state == mret_operate) ?  2'b00  :  2'b11  ;
                mstatus[`MSTATUS_MPIE] <= (csr_state == mret_operate) ?  1'b1  :  mstatus[`MSTATUS_MIE]  ;
                mstatus[`MSTATUS_MIE]  <= (csr_state == mret_operate) ?  mstatus[`MSTATUS_MPIE]  :  1'b0  ;
                mepc   <= (csr_state == mret_operate) ?  mepc  :  mepc_pre  ;
                mcause <= (csr_state == mret_operate) ?  mcause  :  (csr_state == env_call_operate) ?  `ecall_from_m_mode  :  `machine_timer_interrupt  ;
                mcycle <= mcycle + 64'b1;
                if(~stall[3] & inst_valid[3]) minstret <= minstret + 64'b1;
                mip[`MIP_MTIP] <= 0;
            end
            else begin
                mcycle <= mcycle + 64'b1;
                if(~stall[3] & inst_valid[3]) minstret <= minstret + 64'b1;
                mip[`MIP_MTIP] <= 0;
            end
        end
    end

    wire   t_intr = intr & mie[`MIE_MTIE] & mstatus[`MSTATUS_MIE] & ~inst_ecall & pc_valid;
    assign exception_jump_flag = t_intr & ~mem_w_req & ~mem_r_req | inst_ecall | inst_mret;
    assign exception_jump_addr = inst_mret ? {mepc[`MXLEN-1:2],2'b0} : {mtvec[`MXLEN-1:2],2'b0};

    //----------Read Port----------//
    assign csr_r_data = (reset | ~csr_r_en)             ?  `ZERO_WORD               : 
                        (csr_r_addr == `mhartid_addr)   ?  mhartid                  :
                        (csr_r_addr == `mstatus_addr)   ?  mstatus                  :
                        (csr_r_addr == `mie_addr)       ?  mie                      :
                        (csr_r_addr == `mtvec_addr)     ?  mtvec                    :
                        (csr_r_addr == `mscratch_addr)  ?  mscratch                 :
                        (csr_r_addr == `mepc_addr)      ?  {mepc[`MXLEN-1:2],2'b0}  :
                        (csr_r_addr == `mcause_addr)    ?  mcause                   :
                        (csr_r_addr == `mip_addr)       ?  mip                      :
                        (csr_r_addr == `mcycle_addr)    ?  mcycle                   :
                        (csr_r_addr == `minstret_addr)  ?  minstret                 : 
                        (csr_r_addr == `mtimefreq_addr) ?  { 58'b0 , mtimefreq }    :  64'b0  ;

endmodule